/*
 * Serial input and output for Flying Fox
 *
 * by Zik Saleeba 2009-11-23
 */

#include "ffox.h"
#include "board.h"
#include "servo.h"
#include "receiver.h"

/* defines */
#define SERIAL_GPS_TX_BUFSIZE 64
#define SERIAL_GPS_RX_BUFSIZE 1024
#define SERIAL_RADIO_TX_BUFSIZE 1024
#define SERIAL_RADIO_RX_BUFSIZE 256
#if AUXSER_ENABLE
#define SERIAL_AUXSER_TX_BUFSIZE 64
#define SERIAL_AUXSER_RX_BUFSIZE 64
#else
#define SERIAL_AUXSER_TX_BUFSIZE 1
#define SERIAL_AUXSER_RX_BUFSIZE 1
#endif

#define SERIAL_TX_BUFFER(p) (((p) == GPS_PORT) ? &SerialGPSTxBuffer[0] : (((p) == RADIO_PORT) ? &SerialRadioTxBuffer[0] : &SerialAuxTxBuffer[0]))
#define SERIAL_RX_BUFFER(p) (((p) == GPS_PORT) ? &SerialGPSRxBuffer[0] : (((p) == RADIO_PORT) ? &SerialRadioRxBuffer[0] : &SerialAuxRxBuffer[0]))
#define SERIAL_TX_BUFFER_SIZE(p) (((p) == GPS_PORT) ? SERIAL_GPS_TX_BUFSIZE : (((p) == RADIO_PORT) ? SERIAL_RADIO_TX_BUFSIZE : SERIAL_AUXSER_TX_BUFSIZE))
#define SERIAL_RX_BUFFER_SIZE(p) (((p) == GPS_PORT) ? SERIAL_GPS_RX_BUFSIZE : (((p) == RADIO_PORT) ? SERIAL_RADIO_RX_BUFSIZE : SERIAL_AUXSER_RX_BUFSIZE))


/* serial internal structure */
typedef struct
{
    xSemaphoreHandle writeLock;
    
    xSemaphoreHandle txQueueEmpty;
    int waitingOnTxQueueEmpty;
    
    uint8_t *TxBuf;
    int TxBufSize;
    int TxHead;

    uint8_t *RxBuf;
    int RxBufSize;
    int RxTail;
    
    USART_TypeDef *USARTPort;
    DMA_Channel_TypeDef *TxDMAChannel;
    DMA_Channel_TypeDef *RxDMAChannel;
    
} SerialPortInfo;


/* buffers for transmit and receive - separate so we can have different sized buffers for each */
uint8_t SerialGPSTxBuffer[SERIAL_GPS_TX_BUFSIZE];
uint8_t SerialGPSRxBuffer[SERIAL_GPS_RX_BUFSIZE];
uint8_t SerialRadioTxBuffer[SERIAL_RADIO_TX_BUFSIZE];
uint8_t SerialRadioRxBuffer[SERIAL_RADIO_RX_BUFSIZE];
uint8_t SerialAuxTxBuffer[SERIAL_AUXSER_TX_BUFSIZE];
uint8_t SerialAuxRxBuffer[SERIAL_AUXSER_RX_BUFSIZE];


SerialPortInfo SerialPort[NUM_SERIAL_PORTS];


/*
 * NAME:        SerialDMAInit
 * ACTION:      Initialise the DMA for a serial port
 * PARAMETERS:  int Port - the port: GPS_PORT, RADIO_PORT, AUXSER_PORT etc.
 */
 
void SerialDMAInit(int SerialPort)
{
    DMA_InitTypeDef DMA_InitStructure;
    SerialPortInfo *Ser = &SerialPort[Port];

    /* GPS transmit DMA channel - transmit from circular buffer */
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_DeInit(Ser->TxDMAChannel);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&Ser->USARTPort->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Ser->TxBuf;
    DMA_InitStructure.DMA_BufferSize = Ser->TxBufSize;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_Init(Ser->TxDMAChannel, &DMA_InitStructure);
    
    /* GPS receive DMA channel - to circular buffer */
    DMA_DeInit(Ser->RxDMAChannel);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&Ser->USARTPort->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Ser->RxBuf;
    DMA_InitStructure.DMA_BufferSize = Ser->RxBufSize;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_Init(Ser->RxDMAChannel, &DMA_InitStructure);
}


/*
 * NAME:        SerialInit
 * ACTION:      Initialise the serial ports
 */
 
void SerialInit()
{
    int Count;
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    SerialPortInfo *Ser = &SerialPort[Port];
    
    /* initialise the structures */
    for (Count = 0; Count < NUM_SERIAL_PORTS; Count++)
    {
        Ser = &SerialPort[Count];
        Ser->writeLock
        Ser->txQueueEmpty
        Ser->TxHead = 0;
        Ser->RxTail = 0;
        
        Ser->writeLock = xSemaphoreCreateMutex();

    }
    Ser = &SerialPort[GPS_PORT];
    Ser->TxBufSize = SERIAL_GPS_TX_BUFSIZE;
    Ser->RxBuf = &SerialGPSRxBuffer[0];
    Ser->RxBufSize = SERIAL_GPS_RX_BUFSIZE;
    Ser->TxDMAChannel = GPS_TX_DMA_CHANNEL;
    Ser->RxDMAChannel = GPS_RX_DMA_CHANNEL;

    Ser = &SerialPort[RADIO_PORT];
    Ser->TxBuf = &SerialRadioTxBuffer[0];
    Ser->TxBufSize = SERIAL_RADIO_TX_BUFSIZE;
    Ser->RxBuf = &SerialRadioRxBuffer[0];
    Ser->RxBufSize = SERIAL_RADIO_RX_BUFSIZE;
    Ser->TxDMAChannel = RADIO_TX_DMA_CHANNEL;
    Ser->RxDMAChannel = RADIO_RX_DMA_CHANNEL;
    
    Ser = &SerialPort[AUXSER_PORT];
    Ser->TxBuf = &SerialAuxTxBuffer[0];
    Ser->TxBufSize = SERIAL_AUXSER_PORT_TX_BUFSIZE;
    Ser->RxBuf = &SerialAuxRxBuffer[0];
    Ser->RxBufSize = SERIAL_AUXSER_PORT_RX_BUFSIZE;
    Ser->TxDMAChannel = AUXSER_TX_DMA_CHANNEL;
    Ser->RxDMAChannel = AUXSER_RX_DMA_CHANNEL;
    
    /* DMA clock enable */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* USART clock enable */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB1Periph_USART3, ENABLE); 

    /* enable serial interrupts */
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_Init(&NVIC_InitStructure);

    /* USART GPIO configuration */
    GPIO_PinRemapConfig(GPIO_PartialRemap_USART3, ENABLE);
    
    /* Configure GPS Rx (USART2) as input with a pull-up */
    GPIO_InitStructure.GPIO_Pin = GPS_RXD_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPS_GPIO_PORT, &GPIO_InitStructure);
    
    /* Configure GPS Tx (USART2) as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = GPS_TXD_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPS_GPIO_PORT, &GPIO_InitStructure);
    
    /* Configure radio Rx (USART3) as input with a pull-up */
    GPIO_InitStructure.GPIO_Pin = RADIO_RXD_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(RADIO_GPIO_PORT, &GPIO_InitStructure);  
    
    /* Configure radio Tx (USART3) as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = RADIO_TXD_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(RADIO_GPIO_PORT, &GPIO_InitStructure);  

    /* set up DMA */
    SerialDMAInit(GPS_PORT);
    SerialDMAInit(RADIO_PORT);
    
    /* set the initial baud rate */
    Serial_SetMode(GPS_PORT, GPS_BAUD, GPS_HARDWARE_FLOW_CONTROL);
    Serial_SetMode(RADIO_PORT, RADIO_BAUD, RADIO_HARDWARE_FLOW_CONTROL);
    
#if AUXSER_ENABLE
    /* enable the clock for AUXSER */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    /* Configure AUXSER Rx (USART1) as input with a pull-up */
    GPIO_InitStructure.GPIO_Pin = AUXSER_RXD_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(AUXSER_GPIO_PORT, &GPIO_InitStructure);  
    
    /* Configure AUXSER Tx (USART1) as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = AUXSER_TXD_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(AUXSER_GPIO_PORT, &GPIO_InitStructure);  

    /* enable interrupts */
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_Init(&NVIC_InitStructure);

    /* set up DMA */
    SerialDMAInit(AUXSER_USART, AUXSER_TX_DMA_CHANNEL, AUXSER_RX_DMA_CHANNEL);

    /* set the baud rate */
    Serial_SetMode(AUXSER_PORT, AUXSER_BAUD, AUXSER_HARDWARE_FLOW_CONTROL);
#endif
}


/*
 * NAME:        Serial_SetMode
 * ACTION:      Set the baud rate and handshaking for a serial port
 * PARAMETERS:  int Port - the port: GPS_PORT, RADIO_PORT, AUXSER_PORT etc.
 *              int Baud - the baud rate in bps
 *              int HardwareHandshake - TRUE for hardware handshaking
 */
 
void Serial_SetMode(int Port, int Baud, int HardwareHandshake)
{
    USART_InitTypeDef USART_InitStructure;
    SerialPortInfo *Ser = &SerialPort[Port];
    USART_TypeDef *USART = Ser->USARTPort;
    
    /* Configure USART */
    USART_DeInit(USART);
    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = Baud;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = HardwareHandshake ? USART_HardwareFlowControl_RTS_CTS : USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    
    USART_Init(USART, &USART_InitStructure);
    
    /* Enable USART DMA Tx/Rx requests */
    USART_DMACmd(USART, USART_DMAReq_Tx, ENABLE);
    USART_DMACmd(USART, USART_DMAReq_Rx, ENABLE);
    
    /* Enable USART */
    USART_Cmd(USART, ENABLE);
    
    /* Enable USART DMA channels */
    DMA_Cmd(Ser->TxDMAChannel, ENABLE);
    DMA_Cmd(Ser->RxDMAChannel, ENABLE);
}


/*	
 * NAME:        Serial_SendChar
 * ACTION:      Sends a character (in the range of 0 to 255) to the write 
 *              queue. Will wait for space to become available if necessary.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              int Ch - The character to be sent.  
 *                  Must be 0 <= c < 256.
 * RETURNS:     0 on success, -1 on queue full
 */
 
int Serial_SendChar(int Port, int Ch)
{
    unsigned char c;
    SerialPortInfo *Ser = &SerialPort[Port];

    /* try to get write access */
    if (xSemaphoreTake(Ser->writeLock, portMAX_DELAY) != pdTRUE)
        return -1;

    /* add this character to the send queue */ 
    if (Ser->USARTPort->)
    {
        Ser->waitingOnTxQueueEmpty = false;
        vSemaphoreCreateBinary(SerialDev->txQueueEmpty);
    }
    // XXX AT91F_US_EnableIt(USART_PERIPHERAL(Port), AT91C_US_TXRDY); 
    
    /* release write access */
    xSemaphoreGive(Ser->writeLock);
    
    return 0;
}


/*
 * NAME:        Serial_GetReadable
 * ACTION:      Returns the number of bytes in the queue waiting to be read.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 * RETURNS:     int - bytes in the receive queue
 */
 
int Serial_GetReadable(int Port)
{
    return uxQueueMessagesWaiting(Serial[Port].receiveQueue);
}


/*
 * NAME:        Serial_GetChar
 * ACTION:      Gets a single character from the queue or waits for one to
 *              arrive.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              int timeout - Time in milliseconds to block waiting for the 
 *                  specified number of bytes. 0 means don't wait, just poll.
 *                  -1 means wait indefinitely.
 * RETURNS:     int - character form the queue having a value of 0 - 255.  
 *                  The return value is -1 if there was a timeout waiting
 *                  for a character to arrive.
 */
 
int Serial_GetChar(int Port, int timeout)
{
    unsigned char c;
    portTickType tickwait;
    
    /* set the timeout in ticks */
    if (timeout == 0)
    {
        tickwait = 0;
        
        /* check if there's anything in the queue */
        if (uxQueueMessagesWaiting(Serial[Port].receiveQueue) == 0)
            return -1;
    }
    else 
        tickwait = Serial_TickwaitForTimeout(timeout);

    if (xQueueReceive(Serial[Port].receiveQueue, &c, tickwait) == pdTRUE)
        return (int)c;
    else
        return -1;
}


/*
 * NAME:        Serial_Write
 * ACTION:      Write a block of data to the Serial port.  Will block for the 
 *              time specified (in ms) if the queue fills up.
 * PARAMETERS:  int Port - the serial port to use (0 or 1)
 *              char *buffer - a pointer to the buffer to write from
 *              int count - an integer specifying the number of bytes to write
 *              int timeout - time in milliseconds to block waiting for the 
 *                  queue to free up. 0 means don't wait.
 * RETURNS:     0 on success, -1 on queue full and timed-out
 */
int Serial_Write(int Port, const char *buffer, int count, int timeout)
{
    portTickType tickwait;
        
    /* set the timeout in ticks */
    tickwait = Serial_TickwaitForTimeout(timeout);
    
    /* try to get write access */
    if (xSemaphoreTake(Serial[Port].writeLock, tickwait) != pdTRUE)
        return -1;

    /* send each character */
    while (count)
    {
        if (xQueueSend(Serial[Port].transmitQueue, buffer++, tickwait) != pdTRUE) 
            return -1; 
        count--;
    }
    
    /* Turn on the Tx interrupt so the ISR will remove the character from the 
     * queue and send it. This does not need to be in a critical section as 
     * if the interrupt has already removed the character the next interrupt 
     * will simply turn off the Tx interrupt again. */ 
    // XXX AT91F_US_EnableIt(USART_PERIPHERAL(Port), AT91C_US_TXRDY); 
    
    /* release write access */
    xSemaphoreGive(Serial[Port].writeLock);
    
    return 0;
}


/*
 * NAME:        Serial_Read
 * ACTION:      Read data from the Serial port.  Will block for the time 
 *              specified (in ms) if there are insufficient characters.  
 *              Blocking can be avoided if Serial_GetReadable() is used 
 *              to determine how many characters are available to read prior 
 *              to calling this function.
 * PARAMETERS:  int Port - which serial port to use (0 or 1)
 *              char *buffer - A pointer to the buffer to read into.
 *              int size - An integer specifying the maximum number of bytes to 
 *                          read
 *              int timeout - Time in milliseconds to block waiting for the 
 *                          specified number of bytes. 0 means don't wait.
 *                          -1 means wait indefinitely.
 * RETURNS:     int - number of bytes read (>=0)
 */
 
int Serial_Read(int Port, char *buffer, int size, int timeout)
{
    int count;
    int result = pdTRUE;
    portTickType tickwait;
    
    /* set the timeout in ticks */
    tickwait = Serial_TickwaitForTimeout(timeout);

    /* read the characters one by one */
    for (count = 0; count < size && result == pdTRUE; count++, buffer++)
    {
        /* wait for the character to be available in the receive queue */ 
        result = xQueueReceive(Serial[Port].receiveQueue, buffer, tickwait);
    }
    
    return count;
}


/*
 * NAME:        Serial_WaitSent
 * ACTION:      Wait until all pending data has been sent on the serial port
 * PARAMETERS:  int Port - the serial port to use (0 or 1)
 *              int timeout - time in milliseconds to block waiting for the 
 *                  queue to free up. 0 means don't wait.
 * RETURNS:     0 on success, -1 on error or timeout
 */
int Serial_WaitSent(int Port, int timeout)
{
    portTickType tickwait;
    
    /* set the timeout in ticks */
    tickwait = Serial_TickwaitForTimeout(timeout);

    Serial[Port].waitingOnTxQueueEmpty = true;
    if (xSemaphoreTake(Serial[Port].txQueueEmpty, tickwait) == pdTRUE)
        return 0;

    /* this is a hack. I don't know why it's necessary */    
    vTaskDelay(100/portTICK_RATE_MS);

    return -1;
}
